#include "types.h"
#include "param.h"
#include "kthread.h"
#include "fcntl.h"
#include "user.h"

#define BUFF_SIZE 256
#define SALAD 0
#define PASTA 1
#define STEAK 2
#define NUMBEROFWAITERS 3
#define LONG_EATING_PROCESS 100000
#define SHORT_EATING_PROCESS 1000

// Global fields //
int fd;
int NUM_OF_THREADS, MAX_SEATS, MAX_SALAD, MAX_PASTA, MAX_STEAK;
int initialStudents, joiningStudents, finishedStudents, waiters, seats;
int maxFood[3]; // holds the maximum number per food
int food[3]; // holds the current number of food.
int waitersId[3]; 
int totalRunTime;
int avgRunTime;
int avgWaitTime;
// ***********  //

// Condition Variables //
int condAllWaitersFinished; // CV when all the waiters finished
int condSeats; // CV when add/substract seats
int condStudentFood[3]; // CV when produce/consume food type
int condWaitersFood[3];

// Mutexes //
int mtxWaiters; // mutex for the waiters resource
int mtxFood[3]; // mutexes for the food resources
int mtx_Seats; // mutex for the seats resource

int mtx_FinishedStudents; // mutex for the finishedStudents field

/*
 * Initialize the mutexes and the condition variables.
 */
void initialize(){
	condSeats = kthread_cond_alloc();
	mtx_Seats = kthread_mutex_alloc();
	mtxWaiters = kthread_mutex_alloc();
	
	mtxFood[0] = kthread_mutex_alloc();
	mtxFood[1] = kthread_mutex_alloc();
	mtxFood[2] = kthread_mutex_alloc();
	
	condStudentFood[0] = kthread_cond_alloc();
	condStudentFood[1] = kthread_cond_alloc();
	condStudentFood[2] = kthread_cond_alloc();
	
	condWaitersFood[0] = kthread_cond_alloc();
	condWaitersFood[1] = kthread_cond_alloc();
	condWaitersFood[2] = kthread_cond_alloc();
	
	condAllWaitersFinished = kthread_cond_alloc();
	mtx_FinishedStudents = kthread_mutex_alloc();
}

int
length(void* arr){
    return sizeof(arr)/sizeof(int);
}

int
readConfFile(int res[]){

    int fdRead, resIndex;
    char buff[BUFF_SIZE];
    char input[2] = {'0','\0'};
    
    fdRead = open("ass2_conf.txt", O_RDONLY);
    if(fdRead < 0)
		return -1;
    
    resIndex = 0;
    int position = 0;
    while (read(fdRead,input,1) != 0){
		if (strcmp(input,"\n") == 0){
			buff[position] = '\0';
			res[resIndex] = atoi(buff);
			resIndex++;
			position=0;
		}
		else{
		   buff[position] = input[0];
		   position++;
		}
    }
    buff[position] = '\0';
    res[resIndex] = atoi(buff);
    close(fdRead);
    return 0;
}


void
student_eat(int timeToEat){
	int i,j,k;
	int t;
	for(i=1;i<=timeToEat;i++){
		for(j=1;j<=1000;j++){
			t=1;
			for(k=1;k<=20;k++){
				t*=k;
			}
		}
	}
}

void
student_take_food(){

	int studentId = kthread_id();
	int foodTaken = 0;
	int cFood;
	
	// first take food number (studentId)%3
	// then take food number (studentId+1)%3
	cFood = (studentId)%3;
	kthread_mutex_lock(mtxFood[cFood]);
	while(foodTaken==0){
		if (food[cFood]>0){
			--food[cFood];
			++foodTaken;
			printf(fd,"Student %d acquired %d\n",studentId,cFood);
			printf(2,"Student %d acquired %d\n",studentId,cFood);
			kthread_cond_signal(condWaitersFood[cFood]);
			kthread_mutex_unlock(mtxFood[cFood]);
		}else{
			printf(fd,"Student %d waits for %d\n",studentId,cFood);
			printf(2,"Student %d waits for %d\n",studentId,cFood);
			kthread_cond_wait(condStudentFood[cFood],mtxFood[studentId%3]);
		}
	}
	cFood = (studentId+1)%3;
	kthread_mutex_lock(mtxFood[cFood]);
	while(foodTaken==1){
		if (food[cFood]>0){
			--food[cFood];
			++foodTaken;
			printf(fd,"Student %d acquired %d\n",studentId,cFood);
			printf(2,"Student %d acquired %d\n",studentId,cFood);
			kthread_cond_signal(condWaitersFood[cFood]);
			kthread_mutex_unlock(mtxFood[cFood]);
		}else{
			printf(fd,"Student %d waits for %d\n",studentId,cFood);
			printf(2,"Student %d waits for %d\n",studentId,cFood);
			kthread_cond_wait(condStudentFood[cFood],mtxFood[cFood]);
		}
	}
	printf(fd,"Student %d started long eating process\n",studentId);
	printf(2,"Student %d started long eating process\n",studentId);
	student_eat(LONG_EATING_PROCESS);
	
	cFood = (studentId+2)%3;
	kthread_mutex_lock(mtxFood[cFood]);
	while (foodTaken<3){
		// last take food number (studentId+2)%3
		if (food[cFood]>0){
			--food[cFood];
			++foodTaken;
			printf(fd,"Student %d acquired %d\n",studentId,cFood);
			printf(2,"Student %d acquired %d\n",studentId,cFood);
			kthread_cond_signal(condWaitersFood[cFood]);
			kthread_mutex_unlock(mtxFood[cFood]);
		}
		else{
			printf(fd,"Student %d waits for %d\n",studentId,cFood);		
			printf(2,"Student %d waits for %d\n",studentId,cFood);		
			kthread_cond_wait(condStudentFood[cFood],mtxFood[cFood]);
		}
	}
	printf(fd,"Student %d started short eating process\n",studentId);
	printf(2,"Student %d started short eating process\n",studentId);
	student_eat(SHORT_EATING_PROCESS);
}

void
notify_waiters(){
	kthread_mutex_lock(mtxFood[0]);
 	kthread_cond_signal(condWaitersFood[0]);
 	kthread_mutex_unlock(mtxFood[0]);
 	kthread_mutex_lock(mtxFood[1]);
 	kthread_cond_signal(condWaitersFood[1]);
 	kthread_mutex_unlock(mtxFood[1]);
 	kthread_mutex_lock(mtxFood[2]);
 	kthread_cond_signal(condWaitersFood[2]);
 	kthread_mutex_unlock(mtxFood[2]);
}

void
student_leave_table(){

	kthread_mutex_lock(mtx_FinishedStudents);
	printf(fd,"Student %d left the table\n",kthread_id());
	printf(2,"Student %d left the table\n",kthread_id());
	--finishedStudents;
	kthread_mutex_unlock(mtx_FinishedStudents);
	
	// wake up the hostess
	kthread_mutex_lock(mtx_Seats);
	kthread_cond_signal(condSeats);
	kthread_mutex_unlock(mtx_Seats);
	
	// waking all waiters who might wait even after the last student has finished
 	notify_waiters();
	
	// exit the thread - student finished
	int totalThreadElapsedTime, totalThreadRunTime, totalThreadWaitTime;
	retrieve_process_statistics(&totalThreadElapsedTime,&totalThreadRunTime,&totalThreadWaitTime);
	avgRunTime+=totalThreadRunTime;
	avgWaitTime+=totalThreadWaitTime;
	
	kthread_exit();
}

void *
student_life_cycle(){
	student_take_food();
	student_leave_table();
	return 0;
}

void *
hostess_enter_students(){
	int studentId[finishedStudents];
	int i = 0;
	int flag = 0;
	
	kthread_mutex_lock(mtx_Seats);
	while (1){
		if (initialStudents==0){
			// if there are no more students, let the joining students enter
			if (initialStudents==0 && flag==0){
				flag=1;
				initialStudents = joiningStudents;
			}
			// if there are no students left, break the loop and exit
			else{
				break;
			}
		// if seats are full - wait
		}else if (seats==MAX_SEATS){
			kthread_cond_wait(condSeats,mtx_Seats);
			--seats;
		}else{
			if (seats<MAX_SEATS){
				void * sStack = malloc(MAX_STACK_SIZE);
				studentId[i] = kthread_create(student_life_cycle,sStack,MAX_STACK_SIZE);
				printf(fd,"Student %d joined the table\n",studentId[i]);
				printf(2,"Student %d joined the table\n",studentId[i]);
				--initialStudents;
				++seats;
				i++;
			}
		}
		
	}
	kthread_mutex_unlock(mtx_Seats);
	
	int j;
	for(j=0; j < i; j++){
		kthread_join(studentId[j]);
	}
	
	notify_waiters(); // wakeup all the waiters
	
	int totalThreadElapsedTime, totalThreadRunTime, totalThreadWaitTime;
	retrieve_process_statistics(&totalThreadElapsedTime,&totalThreadRunTime,&totalThreadWaitTime);
	avgRunTime+=totalThreadRunTime;
	avgWaitTime+=totalThreadWaitTime;
	
	kthread_exit();
	
	return 0;
}

void * 
waiter_refill_food(){
	// cfaw = current food and waiter 
	int cfaw;
	int threadId = kthread_id();
	
	// determine the current waiter
	if (threadId == waitersId[0])
		cfaw = 0;
	else if (threadId == waitersId[1])
		cfaw = 1;
	else
		cfaw = 2;
		
	// lock the shared resource
	kthread_mutex_lock(mtxFood[cfaw]);
	while(1){
		kthread_mutex_lock(mtx_FinishedStudents);
		// if there are no more students left, exit the waiter
		if (finishedStudents==0){
			kthread_mutex_unlock(mtx_FinishedStudents);
			kthread_mutex_unlock(mtxFood[cfaw]);
		
			int totalThreadElapsedTime, totalThreadRunTime, totalThreadWaitTime;
			retrieve_process_statistics(&totalThreadElapsedTime,&totalThreadRunTime,&totalThreadWaitTime);
			avgRunTime+=totalThreadRunTime;
			avgWaitTime+=totalThreadWaitTime;

			kthread_exit();
		}
		kthread_mutex_unlock(mtx_FinishedStudents);
		
		// if there is food needs filling - fill it and notify waiting students
		if (food[cfaw]<maxFood[cfaw]){
			++food[cfaw];
			printf(fd,"Waiter %d increased his buffer to %d/%d\n",cfaw,food[cfaw],maxFood[cfaw]);
			printf(2,"Waiter %d increased his buffer to %d/%d\n",cfaw,food[cfaw],maxFood[cfaw]);
			kthread_cond_signal(condStudentFood[cfaw]);
			kthread_mutex_unlock(mtxFood[cfaw]);
		}
		
		// if there is no food need filling - wait
		kthread_mutex_lock(mtxFood[cfaw]);
		kthread_cond_wait(condWaitersFood[cfaw],mtxFood[cfaw]);
	}
	
	return 0;
}

int
main(int args, char** agrv){

    int wIndex;
    int res[6];

    // reading configuration file
    if (readConfFile(res) < 0){
		return -1;
    }
    
    initialStudents = res[0];
    joiningStudents = res[1];
    finishedStudents = initialStudents+joiningStudents;
    MAX_SEATS = res[2];
    MAX_SALAD = res[3];
    MAX_PASTA = res[4];
    MAX_STEAK = res[5];
    seats = 0;  // number of taken seats
    waiters = NUMBEROFWAITERS;
	
    NUM_OF_THREADS = finishedStudents+4;
	
    food[0] = MAX_SALAD;
    food[1] = MAX_PASTA;
    food[2] = MAX_STEAK;
    maxFood[0] = MAX_SALAD;
    maxFood[1] = MAX_PASTA;
    maxFood[2] = MAX_STEAK;
    
	// initializing all mutexes and cond variables
	initialize();
	
	fd = open("ass2_log.txt", O_CREATE | O_WRONLY);
    if(fd < 0)
		return -1;
	
    // creating threads for waiters
    for (wIndex = 0; wIndex < NUMBEROFWAITERS; wIndex++){
		void * wStack = malloc(MAX_STACK_SIZE);
		waitersId[wIndex] = kthread_create(waiter_refill_food,wStack,MAX_STACK_SIZE); 
    }
    
	// creating thread for hostess
	void * hStack = malloc(MAX_STACK_SIZE);
    int hostessId = kthread_create(hostess_enter_students,hStack,MAX_STACK_SIZE);
	
	
	kthread_join(hostessId);

	int totalThreadElapsedTime, totalThreadRunTime, totalThreadWaitTime;
	retrieve_process_statistics(&totalThreadElapsedTime,&totalThreadRunTime,&totalThreadWaitTime);
	avgRunTime+=totalThreadRunTime;
	avgWaitTime+=totalThreadWaitTime;
	
	printf(fd, "Simulation: kernel-Level-Threads\nTotal run time is: %d\nAverage run time per kernel-level-thread is: %d\nAverage wait time per kernel-level thread is: %d\n",totalThreadElapsedTime,avgRunTime,avgWaitTime);
	printf(2, "Simulation: kernel-Level-Threads\nTotal run time is: %d\nAverage run time per kernel-level-thread is: %d\nAverage wait time per kernel-level thread is: %d\n",totalThreadElapsedTime,avgRunTime,avgWaitTime);
	
	close(fd);
	
    kthread_exit();
	
	return 0;
}

